package org.skatiger.jrove.spring.tags.component;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.skatiger.jrove.ExternalContext;
import org.skatiger.jrove.JroveException;
import org.skatiger.jrove.ViewContext;
import org.skatiger.jrove.component.UIComponent;
import org.skatiger.jrove.spring.tags.SpringComponent;
import org.skatiger.jrove.spring.utils.SpringUrlUtil;
import org.skatiger.jrove.spring.utils.SpringUrlUtil.UrlType;
import org.springframework.util.StringUtils;
import org.springframework.web.util.HtmlUtils;
import org.springframework.web.util.UriUtils;

public class Url extends SpringComponent {
    private static final String URL_TEMPLATE_DELIMITER_PREFIX = "{";

    private static final String URL_TEMPLATE_DELIMITER_SUFFIX = "}";

    private String value;
    private String context;
    private String var;

    public String getValue() {
        return getPropertyValue("value", value);
    }

    public void setVar(String var) {
        this.var = var;
    }

    public void setContext(String context) {
        this.context = context;
    }

    /**
     * Sets the value of the URL
     */
    public void setValue(String value) {
        this.value = value;
    }

    private List<Param> getParams() {
        List<UIComponent> children = getChildren();
        if (children.isEmpty()) {
            return Collections.emptyList();
        }
        List<Param> params = new ArrayList<Param>();
        for (UIComponent component : children) {
            if (component instanceof Param) {
                params.add((Param) component);
            }
        }
        return params;
    }

    public void encodeBegin(org.skatiger.jrove.ViewContext context)
            throws java.io.IOException {

        String url = createUrl();
        if (this.var == null) {
            // print the url to the writer
            context.getResponseWriter().writeText(url, null);
        } else {
            // store the url as a variable
            context.setAttribute(var, url);
        }
    };

    /**
     * Build the URL for the tag from the tag attributes and parameters.
     * 
     * @return the URL value as a String
     * @throws JroveException
     */
    private String createUrl() throws JroveException {
        ExternalContext extctx = getViewContext().getExternalContext();
        StringBuilder url = new StringBuilder();
        String value = getValue();

        UrlType _type = SpringUrlUtil.resolveUrlType(value);
        if (_type == UrlType.CONTEXT_RELATIVE) {
            // add application context to url
            if (this.context == null) {
                url.append(extctx.getRequestContextPath());
            } else {
                url.append(this.context);
            }
        }
        if (_type != UrlType.RELATIVE && _type != UrlType.ABSOLUTE
                && !value.startsWith("/")) {
            url.append("/");
        }
        List<Param> params = getParams();
        Set<String> templateParams = new HashSet<String>();
        url.append(replaceUriTemplateParams(value, params, templateParams));
        url.append(createQueryString(params, templateParams,
                (url.indexOf("?") == -1)));

        String urlStr = url.toString();
        if (_type != UrlType.ABSOLUTE) {
            // Add the session identifier if needed
            // (Do not embed the session identifier in a remote link!)
            urlStr = extctx.encodeURL(urlStr);
        }

        // HTML and/or JavaScript escape, if demanded.
        urlStr = isHtmlEscape() ? HtmlUtils.htmlEscape(urlStr) : urlStr;

        return urlStr;
    }

    /**
     * Build the query string from available parameters that have not already
     * been applied as template params.
     * <p>
     * The names and values of parameters are URL encoded.
     * 
     * @param params
     *            the parameters to build the query string from
     * @param usedParams
     *            set of parameter names that have been applied as template
     *            params
     * @param includeQueryStringDelimiter
     *            true if the query string should start with a '?' instead of
     *            '&'
     * @return the query string
     */
    protected String createQueryString(List<Param> params,
            Set<String> usedParams, boolean includeQueryStringDelimiter)
            throws JroveException {
        ExternalContext extctx = getViewContext().getExternalContext();

        String encoding = extctx.getResponseCharacterEncoding();
        StringBuilder qs = new StringBuilder();
        for (Param param : params) {
            if (!usedParams.contains(param.getName())
                    && StringUtils.hasLength(param.getName())) {
                if (includeQueryStringDelimiter && qs.length() == 0) {
                    qs.append("?");
                } else {
                    qs.append("&");
                }
                try {
                    qs.append(UriUtils.encodeQueryParam(param.getName(),
                            encoding));
                    if (param.getValue() != null) {
                        qs.append("=");
                        qs.append(UriUtils.encodeQueryParam(param.getValue()
                                .toString(), encoding));
                    }
                } catch (UnsupportedEncodingException ex) {
                    throw new JroveException(ex);
                }
            }
        }
        return qs.toString();
    }

    /**
     * do not encode children component
     */
    @Override
    public void encodeChildren(ViewContext context) throws IOException {
    }

    /**
     * Replace template markers in the URL matching available parameters. The
     * name of matched parameters are added to the used parameters set.
     * <p>
     * Parameter values are URL encoded.
     * 
     * @param uri
     *            the URL with template parameters to replace
     * @param params
     *            parameters used to replace template markers
     * @param usedParams
     *            set of template parameter names that have been replaced
     * @return the URL with template parameters replaced
     */
    protected String replaceUriTemplateParams(String uri, List<Param> params,
            Set<String> usedParams) throws JroveException {
        ExternalContext extctx = getViewContext().getExternalContext();

        String encoding = extctx.getResponseCharacterEncoding();
        for (Param param : params) {
            String template = URL_TEMPLATE_DELIMITER_PREFIX + param.getName()
                    + URL_TEMPLATE_DELIMITER_SUFFIX;
            if (uri.contains(template)) {
                usedParams.add(param.getName());
                try {
                    uri = uri.replace(template, UriUtils.encodePath(param
                            .getValue().toString(), encoding));
                } catch (UnsupportedEncodingException ex) {
                    throw new JroveException(ex);
                }
            }
        }
        return uri;
    }

}
